/* Copyright holders: Sarah Walker
   see COPYING for more details
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <wchar.h>
#include <86box/86box.h>
#include <86box/io.h>
#include <86box/lpt.h>
#include <86box/pic.h>
#include <86box/sound.h>
#include <86box/prt_devs.h>
#include <86box/net_plip.h>

lpt_port_t lpt_ports[PARALLEL_MAX];

const lpt_device_t lpt_none_device = {
    .name          = "None",
    .internal_name = "none",
    .init          = NULL,
    .close         = NULL,
    .write_data    = NULL,
    .write_ctrl    = NULL,
    .read_data     = NULL,
    .read_status   = NULL,
    .read_ctrl     = NULL
};

static const struct {
    const char         *internal_name;
    const lpt_device_t *device;
} lpt_devices[] = {
  // clang-format off
    {"none",            &lpt_none_device          },
    {"dss",             &dss_device               },
    {"lpt_dac",         &lpt_dac_device           },
    {"lpt_dac_stereo",  &lpt_dac_stereo_device    },
    {"text_prt",        &lpt_prt_text_device      },
    {"dot_matrix",      &lpt_prt_escp_device      },
    {"postscript",      &lpt_prt_ps_device        },
    {"plip",            &lpt_plip_device          },
    {"dongle_savquest", &lpt_hasp_savquest_device },
    {"",                NULL                      }
  // clang-format on
};

const char *
lpt_device_get_name(int id)
{
    if (strlen(lpt_devices[id].internal_name) == 0)
        return NULL;
    if (!lpt_devices[id].device)
        return "None";
    return lpt_devices[id].device->name;
}

const char *
lpt_device_get_internal_name(int id)
{
    if (strlen(lpt_devices[id].internal_name) == 0)
        return NULL;
    return lpt_devices[id].internal_name;
}

int
lpt_device_get_from_internal_name(char *s)
{
    int c = 0;

    while (strlen(lpt_devices[c].internal_name) != 0) {
        if (strcmp(lpt_devices[c].internal_name, s) == 0)
            return c;
        c++;
    }

    return 0;
}

void
lpt_devices_init(void)
{
    for (uint8_t i = 0; i < PARALLEL_MAX; i++) {
        lpt_ports[i].dt = (lpt_device_t *) lpt_devices[lpt_ports[i].device].device;

        if (lpt_ports[i].dt && lpt_ports[i].dt->init)
            lpt_ports[i].priv = lpt_ports[i].dt->init(&lpt_ports[i]);
    }
}

void
lpt_devices_close(void)
{
    lpt_port_t *dev;

    for (uint8_t i = 0; i < PARALLEL_MAX; i++) {
        dev = &lpt_ports[i];

        if (lpt_ports[i].dt && lpt_ports[i].dt->close)
            dev->dt->close(dev->priv);

        dev->dt = NULL;
    }
}

void
lpt_write(uint16_t port, uint8_t val, void *priv)
{
    lpt_port_t *dev = (lpt_port_t *) priv;

    switch (port & 3) {
        case 0:
            if (dev->dt && dev->dt->write_data && dev->priv)
                dev->dt->write_data(val, dev->priv);
            dev->dat = val;
            break;

        case 1:
            break;

        case 2:
            if (dev->dt && dev->dt->write_ctrl && dev->priv)
                dev->dt->write_ctrl(val, dev->priv);
            dev->ctrl       = val;
            dev->enable_irq = val & 0x10;
            break;

        default:
            break;
    }
}

uint8_t
lpt_read(uint16_t port, void *priv)
{
    uint8_t     ret = 0xff;
    lpt_port_t *dev = (lpt_port_t *) priv;

    switch (port & 3) {
        case 0:
            if (dev->dt && dev->dt->read_data && dev->priv)
                ret = dev->dt->read_data(dev->priv);
            else
                ret = dev->dat;
            break;

        case 1:
            if (dev->dt && dev->dt->read_status && dev->priv)
                ret = dev->dt->read_status(dev->priv) | 0x07;
            else
                ret = 0xdf;
            break;

        case 2:
            if (dev->dt && dev->dt->read_ctrl && dev->priv)
                ret = (dev->dt->read_ctrl(dev->priv) & 0xef) | dev->enable_irq;
            else
                ret = 0xe0 | dev->ctrl | dev->enable_irq;
            break;

        default:
            break;
    }

    return ret;
}

uint8_t
lpt_read_port(int port, uint16_t reg)
{
    lpt_port_t *dev = &(lpt_ports[port]);
    uint8_t ret = lpt_read(reg, dev);

    return ret;
}

uint8_t
lpt_read_status(int port)
{
    lpt_port_t *dev = &(lpt_ports[port]);
    uint8_t ret = 0xff;

    if (dev->dt && dev->dt->read_status && dev->priv)
        ret = dev->dt->read_status(dev->priv) | 0x07;
    else
        ret = 0xdf;

    return ret;
}

void
lpt_irq(void *priv, int raise)
{
    const lpt_port_t *dev = (lpt_port_t *) priv;

    if (dev->enable_irq && (dev->irq != 0xff)) {
        if (raise)
            picint(1 << dev->irq);
        else
            picintc(1 << dev->irq);
    }
}

void
lpt_init(void)
{
    uint16_t default_ports[PARALLEL_MAX] = { LPT1_ADDR, LPT2_ADDR, LPT_MDA_ADDR, LPT4_ADDR };
    uint8_t  default_irqs[PARALLEL_MAX]  = { LPT1_IRQ, LPT2_IRQ, LPT_MDA_IRQ, LPT4_IRQ };

    for (uint8_t i = 0; i < PARALLEL_MAX; i++) {
        lpt_ports[i].addr       = 0xffff;
        lpt_ports[i].irq        = 0xff;
        lpt_ports[i].enable_irq = 0x10;

        if (lpt_ports[i].enabled) {
            lpt_port_init(i, default_ports[i]);
            lpt_port_irq(i, default_irqs[i]);
        }
    }
}

void
lpt_port_init(int i, uint16_t port)
{
    if (lpt_ports[i].enabled) {
        if (lpt_ports[i].addr != 0xffff)
            io_removehandler(lpt_ports[i].addr, 0x0003, lpt_read, NULL, NULL, lpt_write, NULL, NULL, &lpt_ports[i]);
        if (port != 0xffff)
            io_sethandler(port, 0x0003, lpt_read, NULL, NULL, lpt_write, NULL, NULL, &lpt_ports[i]);
        lpt_ports[i].addr = port;
    } else
        lpt_ports[i].addr = 0xffff;
}

void
lpt_port_irq(int i, uint8_t irq)
{
    if (lpt_ports[i].enabled)
        lpt_ports[i].irq = irq;
    else
        lpt_ports[i].irq = 0xff;
}

void
lpt_port_remove(int i)
{
    if (lpt_ports[i].enabled && (lpt_ports[i].addr != 0xffff)) {
        io_removehandler(lpt_ports[i].addr, 0x0003, lpt_read, NULL, NULL, lpt_write, NULL, NULL, &lpt_ports[i]);
        lpt_ports[i].addr = 0xffff;
    }
}

void
lpt1_remove_ams(void)
{
    if (lpt_ports[0].enabled)
        io_removehandler(lpt_ports[0].addr + 1, 0x0002, lpt_read, NULL, NULL, lpt_write, NULL, NULL, &lpt_ports[0]);
}
